/*
 * TextServices / TSDotNet / TSNative
 * Marco Maria Bellocchi, Mauro Bampo
 * Copyright(c) 2013.
 * 
 * The MIT License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 */

// MB 25/01/2013: Created

#include "stdafx.h"
#include "CsvNumRleEncoder.h"

// States for the automaton
#define ST_ZERO		0
#define ST_COMMA	1
#define ST_NUM		2
#define ST_PLUS		3
#define ST_MINUS	4
#define ST_START	5
// Characters
#define CH_ZERO		'0'
#define CH_COMMA	','
#define CH_PLUS		'+'
#define CH_MINUS	'-'
// Macros
#define IS_NUM(c)			((c) >= '0' && (c) <= '9') || (c) == '.'
#define IS_POS_NUM(c)		((c) >= '1' && (c) <= '9') || (c) == '.'
#define GO(s)				{state=(s);}
#define INC_ZEROES			{nZeroes++;}
#define WRITE_FAST(c)		{pOutput[io++]=(c);}  /* Use when indices have been checked */
#define WRITE_SAFE(c)		{if(io<bufferLength){WRITE_FAST(c);}else{outputState.Overflow = true;}}  /* Use from other macros */
#define WRITE_GO(c, s)		{WRITE_SAFE(c); state=(s);}
#define REWIND(c, n)		{if(io-n>=inputState.OutputIndex && pOutput[io-1]==(c)){io-=(n);}}  /* Rewinds the output based on last char written, if not at start. */
#define DELETE_COMMA		{REWIND(CH_COMMA, 1);}
#define DELETE_MINUS		{REWIND(CH_MINUS, 1);}
//#define WRITE_Z_STRING		{WRITE_SAFE('Z');if(io+((int)ceil(log10f(nZeroes)))<bufferLength){int w=sprintf(pOutput+io, "%d,", nZeroes);if(w<((int)ceil(log10f(nZeroes)))){throw exception("Write failed.");}io+=w;}else{outputState.Overflow = true;}}
#define WRITE_Z_STRING		{DELETE_MINUS;WRITE_SAFE('Z');int zl=(int)ceil(log10f(nZeroes));if((io+zl)<bufferLength){itoa(nZeroes,pOutput+io,10);io+=zl;}else{outputState.Overflow=true;}}  /* itoa() faster than spf() */
#define WRITE_ZEROES		{if(nZeroes>0){DELETE_COMMA;DELETE_MINUS;if(nZeroes==1){if(io>inputState.OutputIndex){WRITE_SAFE(',');}WRITE_SAFE('0');}else{WRITE_Z_STRING;}WRITE_SAFE(',');nZeroes=0;}}
#define BAD_STRING			{throw exception("Illegal input string.");}

using namespace TSNative::Filters;

int CsvNumRleEncoder::GetInitialBufferLength(int nInputLength) {
	return nInputLength;
}

FilterState CsvNumRleEncoder::Process(const char* pInput, int inputLength, char** ppBuffer, int bufferLength, FilterState inputState) {
	FilterState outputState;
	outputState.InputIndex = inputState.InputIndex;
	char* pOutput = *ppBuffer;
	int ii = inputState.InputIndex;
	int io = inputState.OutputIndex;
	char cIn = pInput[ii];
	int state = ST_START;  // Same as ST_COMMA, but doesn't write commas to out.
	int nZeroes = 0;
	for(; ii<inputLength && io<bufferLength; ii++) {
		cIn = pInput[ii];
		if(cIn == '\0')
			break;
		
		// We could break up this switch into different functions, but this is probably faster.
		// Order each if...else if...else by INCREASING complexity of the test.
		switch(state) {

		case ST_START:
			if(cIn == CH_PLUS) GO(ST_PLUS)
			else if(cIn == CH_MINUS) GO(ST_MINUS)
			else if(cIn == CH_ZERO) GO(ST_ZERO)
			else if(IS_POS_NUM(cIn)) WRITE_GO(cIn, ST_NUM)
			else BAD_STRING;
			break;

		case ST_ZERO:
			if(cIn == CH_COMMA) {INC_ZEROES; GO(ST_COMMA)}
			else if(cIn == CH_ZERO) /* Noop */;
			else if(IS_POS_NUM(cIn)) {WRITE_ZEROES; WRITE_GO(cIn, ST_NUM)}
			else BAD_STRING;
			break;

		case ST_COMMA:
			if(cIn == CH_PLUS) GO(ST_PLUS)
			else if(cIn == CH_MINUS) {DELETE_COMMA; GO(ST_MINUS)}
			else if(cIn == CH_ZERO) GO(ST_ZERO)
			else if(IS_POS_NUM(cIn)) {WRITE_ZEROES; WRITE_GO(cIn, ST_NUM)}
			else BAD_STRING;
			break;

		case ST_NUM:
			if (cIn == CH_COMMA) WRITE_GO(cIn, ST_COMMA)
			else if(IS_NUM(cIn)) WRITE_FAST(cIn)
			else BAD_STRING;
			break;

		case ST_PLUS:
			if (cIn == CH_ZERO) GO(ST_ZERO)
			else if(IS_POS_NUM(cIn)) {WRITE_ZEROES; WRITE_GO(cIn, ST_NUM)}
			else BAD_STRING;
			break;

		case ST_MINUS:
			if (cIn == CH_ZERO) WRITE_GO(CH_MINUS, ST_ZERO)
			else if(IS_POS_NUM(cIn)) {WRITE_ZEROES; DELETE_COMMA; WRITE_SAFE(CH_MINUS); WRITE_GO(cIn, ST_NUM)}
			else BAD_STRING;
			break;

		default:
			throw exception("State not supported.");

		}
	}
	if(io >= bufferLength && ii < inputLength && cIn != '\0')
		outputState.Overflow = true;
	else {
		REWIND('\0', 1);
		if(state == ST_ZERO) nZeroes++;
		WRITE_ZEROES;
		DELETE_COMMA;
		WRITE_SAFE('\0');
	}
	outputState.InputIndex = ii-1;
	outputState.OutputIndex = io-1;
	return outputState;
}